﻿#if UNITY_EDITOR
#define PROFILER
#endif

using Devil;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Profiling;
using UnityEngine.UI;

namespace GameToolkit.UI
{
    public enum EFocusType
    {
        PanelFocused,
        SelectableFocused,
        Any,
        FallbackUI,
    }

    public partial class PanelManager
    {
        bool mValidateKeyButtons;
        RandomList<KeyButtonGroup> mGroups = new RandomList<KeyButtonGroup>();
        RandomList<KeyButtonAction> mActions = new RandomList<KeyButtonAction>();

        IUIButton RegistKeyButton(Panel panel, Selectable selectable, EFocusType focusType, string inputName, GameInput.EBlockLevel isBlocked, bool supportPointerClick)
        {
            if (focusType == EFocusType.PanelFocused && panel == null)
                return null;
            if (focusType == EFocusType.SelectableFocused && selectable == null)
                return null;
            if (string.IsNullOrEmpty(inputName))
                return null;
            //var btn = GameInput.GetButton(inputName, false);
            //if (btn == null)
            //    return null;
            var groupId = panel == null ? 0 : panel.GetInstanceID();
            KeyButtonAction act = null;
            for (int i = 0; i < mActions.Count; i++)
            {
                var action = mActions[i];
                if (action.IsButton(inputName))
                {
                    act = action;
                    break;
                }
            }
            if (act == null)
            {
                var c = inputName[0];
                GameInput.IButton btn;
                if (c == '-' || c == '+')
                {
                    var axis = GameInput.GetAxis(inputName.Substring(1));
                    btn = axis == null ? null : new ButtonFromAxis(inputName, axis, c == '-');
                }
                else
                {
                    btn = GameInput.GetButton(inputName);
                }
                if (btn == null)
                    return null;
                act = new KeyButtonAction(btn);
                mActions.Add(act);
            }
            KeyButtonGroup grp = null;
            for (int i = mGroups.Count - 1; i >= 0; i--)
            {
                if (mGroups[i].groupId != 0 && mGroups[i].panel == null)
                {
                    mGroups.RemoveAt(i);
                    continue;
                }
                if (mGroups[i].groupId == groupId)
                {
                    grp = mGroups[i];
                    break;
                }
            }
            if (grp == null)
            {
                grp = new KeyButtonGroup(panel);
                mGroups.Add(grp);
            }
            KeyBtn keybtn;
            if (selectable == null)
                keybtn = new InvisibleBtn(grp, act, panel, focusType, isBlocked);
            else
                keybtn = new SelectableBtn(grp, act, focusType, panel, selectable, isBlocked, supportPointerClick);
            grp.buttons.Add(keybtn);
            act.refers++;
            mValidateKeyButtons = true;
            return (IUIButton)keybtn;
        }

        public static IUIButton RegistKeyButton(string inputName, GameInput.EBlockLevel isBlocked = GameInput.EBlockLevel.UI)
        {
            if (Instance != null)
                return Instance.RegistKeyButton(null, null, EFocusType.Any, inputName, isBlocked, false);
            else
                return null;
        }

        public static IUIButton RegistKeyButton(Panel panel, string inputName, GameInput.EBlockLevel isBlocked = GameInput.EBlockLevel.UI)
        {
            if (Instance != null)
                return Instance.RegistKeyButton(panel, null, panel == null ? EFocusType.FallbackUI : EFocusType.PanelFocused, inputName, isBlocked, false);
            else
                return null;
        }

        public static IUIButton RegistKeyButton(Selectable selectable, EFocusType focusType, string inputName,
            GameInput.EBlockLevel isBlocked = GameInput.EBlockLevel.UI, bool supportPointerClick = true)
        {
            if (Instance != null)
            {
                Panel panel = null;
                if (focusType != EFocusType.Any)
                {
                    panel = selectable == null ? null : selectable.GetComponentInParent<Panel>();
                }
                return Instance.RegistKeyButton(panel, selectable, focusType, inputName, isBlocked, supportPointerClick);
            }
            return null;
        }

        public static void UnregistKeyButton(IUIButton btn)
        {
            var keybtn = btn as KeyBtn;
            if (keybtn != null)
            {
                var act = keybtn.action;
                keybtn.group.RemoveKey(keybtn);
                if (act.refers > 0)
                {
                    act.RemoveKey(keybtn);
                    act.refers--;
                }
                if (keybtn is System.IDisposable)
                    ((System.IDisposable)keybtn).Dispose();
                if (Instance != null && act.refers == 0)
                {
                    Instance.mActions.Remove(act);
                    act.Interrupt();
                }
            }
        }


        void ProcessKeyButtons(float deltaTime)
        {
            Panel top = ForegroundPanel;
            if (mValidateKeyButtons && mProgressives.Count == 0)
            {
                mValidateKeyButtons = false;
                for (int i = 0; i < mActions.Count; i++)
                {
                    mActions[i].Interrupt();
                }
                for (int i = 0; i < mGroups.Count; i++)
                {
                    var grp = mGroups[i];
                    if (grp.IsActive(top))
                    {
                        for (int k = 0; k < grp.buttons.Count; k++)
                        {
                            var btn = grp.buttons[k];
                            btn.action.JoinKey(btn);
                        }
                    }
                }
            }
            //else
            //{
            for (int i = mActions.Count - 1; i >= 0; i--)
            {
                var act = mActions[i];
                if (act == null)
                    continue;
                //try {
                act.Update(deltaTime, top, mProgressives.Count == 0);
                //}
                //catch(System.Exception e)
                //{
                //    Debug.LogException(e);
                //}
            }

            mValidateKeyButtons |= mProgressives.Count > 0;
            //}
        }

        public interface IUIButton
        {
            bool enabled { get; set; }
            GameInput.IButton Button { get; }
            void AddCallback(System.Action onClick);
            void RemoveCallback(System.Action onClick);
        }

        public class ButtonEventData : PointerEventData
        {
            public GameInput.IButton Button { get; private set; }
            public ButtonEventData(GameInput.IButton btn) : base(EventSystem.current)
            {
                Button = btn;
                button = InputButton.Left;
            }
        }

        abstract class KeyBtn
        {
            public readonly int sortIndex;
            public readonly KeyButtonAction action;
            public readonly KeyButtonGroup group;
            public readonly GameInput.EBlockLevel blockLv;
            public GameInput.IButton Button { get { return action == null ? null : action.Button; } }
            public bool enabled { get; set; }

            public static int CalculateSortIndex(Panel panel, Selectable seletable, EFocusType type)
            {
                int sort = 0;
                if (type == EFocusType.FallbackUI)
                    sort += 100000;
                else if (type == EFocusType.PanelFocused)
                    sort += 200000;
                else if (type == EFocusType.SelectableFocused)
                    sort += 300000;
                if (panel != null)
                {
                    if (panel.PanelType == EPanelType.Dialog)
                        sort += 30000;
                    else if (panel.PanelType == EPanelType.Normal)
                        sort += 20000;
                    else
                        sort += 10000;
                }
                if (seletable != null)
                    sort += 40000;
                return sort;
            }

            public KeyBtn(KeyButtonGroup grp, KeyButtonAction action, int sortIndex, GameInput.EBlockLevel isBlocked)
            {
                this.group = grp;
                this.action = action;
                this.sortIndex = sortIndex;
                this.blockLv = isBlocked;
                enabled = true;
            }

            public abstract bool IsActive(Panel activePanel);
            public abstract void KeyDown(BaseEventData evData);
            public abstract void KeyUp(BaseEventData evData, bool click);
        }

        class SelectableBtn : KeyBtn, IUIButton, System.IDisposable
        {
            public readonly Selectable selectable;
            ClickHandler pointerClick;
            public readonly EFocusType focus;
            System.Action onClick;
            bool mSupportPointerClick;

            public SelectableBtn(KeyButtonGroup grp,
                KeyButtonAction action, EFocusType type, Panel panel, Selectable selectable, GameInput.EBlockLevel blocked, bool pointerClick)
                : base(grp, action, CalculateSortIndex(panel, selectable, type), blocked)
            {
                this.mSupportPointerClick = pointerClick;
                this.focus = type;
                this.selectable = selectable;
            }

            public override bool IsActive(Panel activePanel)
            {
                if (!enabled || GameInput.GetBlockLevel() >= blockLv)
                    return false;
                if (focus == EFocusType.Any)
                    return true;
                if (focus == EFocusType.SelectableFocused)
                    return selectable != null && EventSystem.current.currentSelectedGameObject == selectable.gameObject;
                return true;
            }

            public override void KeyDown(BaseEventData evData)
            {
                //ExecuteEvents.Execute(gameObject, evData, ExecuteEvents.pointerEnterHandler);
                if (mSupportPointerClick)
                {
#if PROFILER
                    Profiler.BeginSample($"[{Button.InputName}] Key Down", selectable.gameObject);
#endif
                    ExecuteEvents.Execute(selectable.gameObject, evData, ExecuteEvents.pointerDownHandler);
#if PROFILER
                    Profiler.EndSample();
#endif
                }
            }

            public override void KeyUp(BaseEventData evData, bool click)
            {
#if PROFILER
                Profiler.BeginSample($"[{Button.InputName}] Key Up", selectable.gameObject);
#endif
                if (mSupportPointerClick)
                {
                    ExecuteEvents.Execute(selectable.gameObject, evData, ExecuteEvents.pointerUpHandler);
                    //ExecuteEvents.Execute(gameObject, mData, ExecuteEvents.pointerExitHandler);
                }
                if (click && enabled)
                {
                    if (mSupportPointerClick)
                    {
                        ExecuteEvents.Execute(selectable.gameObject, evData, ExecuteEvents.pointerClickHandler);
                    }
                    else if (onClick != null)
                    {
#if PROFILER
                        Profiler.BeginSample($"[{Button.InputName}]{onClick.GetDelegateName()}", selectable.gameObject);
#endif
                        onClick.Invoke();
#if PROFILER
                        Profiler.EndSample();
#endif
                    }

                }
#if PROFILER
                Profiler.EndSample();
#endif
            }

            public void AddCallback(System.Action onClick)
            {
                if (pointerClick == null && mSupportPointerClick)
                {
                    pointerClick = ComponentUtil.GetOrAddComponent<ClickHandler>(selectable.gameObject);
                    pointerClick.onClick += OnLegencyButtonClick;
                }
                this.onClick += onClick;
            }

            public void RemoveCallback(System.Action onClick)
            {
                this.onClick -= onClick;
            }

            public void Dispose()
            {
                if (pointerClick != null)
                    pointerClick.onClick -= OnLegencyButtonClick;
                onClick = null;
            }

            void OnLegencyButtonClick()
            {
                if (mSupportPointerClick && onClick != null)
                {
#if PROFILER
                    Profiler.BeginSample($"[{Button.InputName}]{onClick.GetDelegateName()}", selectable.gameObject);
#endif
                    onClick.Invoke();
#if PROFILER
                    Profiler.EndSample();
#endif
                }
            }

        }

        class InvisibleBtn : KeyBtn, IUIButton, System.IDisposable
        {
            System.Action onClick;
            public InvisibleBtn(KeyButtonGroup grp, KeyButtonAction action, Panel panel, EFocusType focus, GameInput.EBlockLevel blocked)
                : base(grp, action, CalculateSortIndex(panel, null, focus), blocked)
            { }

            public void AddCallback(System.Action onClick)
            {
                this.onClick += onClick;
            }

            public void RemoveCallback(System.Action onClick)
            {
                this.onClick -= onClick;
            }
            public override bool IsActive(Panel activePanel)
            {
                return enabled && GameInput.GetBlockLevel() < blockLv;
                //return !isBlocked || GameInput.GetBlocker() == null;
            }

            public override void KeyDown(BaseEventData evData)
            {
            }

            public override void KeyUp(BaseEventData evData, bool click)
            {
                if (click && enabled && onClick != null)
                {
#if PROFILER
                    Profiler.BeginSample($"[{Button.InputName}]{onClick.GetDelegateName()}");
#endif
                    onClick();

#if PROFILER
                    Profiler.EndSample();
#endif
                }
            }

            public void Dispose()
            {
                onClick = null;
            }
        }

        class KeyButtonGroup
        {
            //public readonly int sortIndex;
            public readonly Panel panel;
            public readonly RandomList<KeyBtn> buttons;
            public readonly int groupId;

            public KeyButtonGroup(Panel panel)
            {
                this.panel = panel;
                this.groupId = panel == null ? 0 : panel.GetInstanceID();
                //this.sortIndex = panel == null ? 0 : (1 + (int)panel.m_Mode);
                buttons = new RandomList<KeyBtn>(16, (x, y) => x == y);
            }

            public bool IsActive(Panel activePanel)
            {
                if (groupId == 0)
                    return true;
                if (panel == null)
                    return false;
                return panel.PanelType == EPanelType.Status || /*panel.m_Mode == EPanelMode.TopStatus ||*/ panel == activePanel;
            }
            public void RemoveKey(KeyBtn key)
            {
                buttons.Remove(key);
            }
        }

        class KeyButtonAction
        {
            KeyBtn mCurrentSelection;
            ButtonEventData mEvData;
            public uint refers;

            SortedQueue<KeyBtn> mKeys;

            //public object InputSrouce { get; private set; }
            public GameInput.IButton Button { get { return mEvData.Button; } }

            public KeyButtonAction(GameInput.IButton btn)
            {
                mEvData = new ButtonEventData(btn);
                mKeys = new SortedQueue<KeyBtn>(GetSortIndex);
            }

            public bool IsButton(string btnName)
            {
                var c = btnName[0];
                if (c == '-' || c == '+')
                {
                    var nig = c == '-';
                    return mEvData.Button is ButtonFromAxis && ((ButtonFromAxis)mEvData.Button).IsNigative == nig && mEvData.Button.InputName == btnName.Substring(1);
                }
                else
                    return mEvData.Button.InputName == btnName;
            }

            int GetSortIndex(KeyBtn btn)
            {
                return btn.sortIndex;
            }

            KeyBtn GetKeyDown(Panel activePanel)
            {
                for (int i = mKeys.Count - 1; i >= 0; i--)
                {
                    var key = mKeys[i];
                    if (key.IsActive(activePanel))
                        return key;
                }
                return null;
            }

            public void Update(float deltaTime, Panel activePanel, bool sendClick)
            {
                if (mEvData.Button is ButtonFromAxis)
                {
                    ((ButtonFromAxis)mEvData.Button).OnTick(deltaTime);
                }
                if (mEvData.Button.IsDown)
                {
                    if (mCurrentSelection != null)
                    {
                        mCurrentSelection.KeyUp(mEvData, false);
                        mCurrentSelection = null;
                    }
                    mCurrentSelection = GetKeyDown(activePanel);
                    if (mCurrentSelection != null)
                        mCurrentSelection.KeyDown(mEvData);
                }
                if (mEvData.Button.IsUp)
                {
                    if (mCurrentSelection != null)
                        mCurrentSelection.KeyUp(mEvData, sendClick);
                    mCurrentSelection = null;
                }
                if (mCurrentSelection != null && !mEvData.Button.HasButton)
                {
                    mCurrentSelection.KeyUp(mEvData, false);
                    mCurrentSelection = null;
                }
            }

            public void Interrupt()
            {
                if (mCurrentSelection != null)
                {
                    mCurrentSelection.KeyUp(mEvData, false);
                    mCurrentSelection = null;
                }
                mKeys.Clear();
            }

            public void JoinKey(KeyBtn key)
            {
                mKeys.Add(key);
            }

            public void RemoveKey(KeyBtn key)
            {
                if (mKeys.Remove(key) && key == mCurrentSelection)
                {
                    mCurrentSelection.KeyUp(mEvData, false);
                    mCurrentSelection = null;
                }
            }

        }


        public class ButtonFromAxis : GameInput.IButton
        {
            GameInput.IAxis mAxis;
            bool mNegative;
            bool mHasBtn;
            float mTime;

            public string Title { get { return mAxis.Title; } }

            public string InputName { get; private set; }

            public bool IsDown { get; private set; }

            public bool IsUp { get; private set; }

            public bool HasButton { get; private set; }

            public bool IsReadonly { get { return true; } }
            public bool IsNigative { get { return mNegative; } }
            public GameInput.IAxis Axis { get { return mAxis; } }

            public ButtonFromAxis(string inputName, GameInput.IAxis axis, bool negative)
            {
                mAxis = axis;
                mNegative = negative;
                this.InputName = inputName;
                mHasBtn = mNegative ? (mAxis.Value < -0.5f) : (mAxis.Value > 0.5f);
                if (mHasBtn)
                    mTime = 0.6f;
            }

            public InputController.KeyInfo GetKeyInfo(InputController controller)
            {
                InputController.KeyInfo nig, pos;
                mAxis.GetKeyInfo(controller, out nig, out pos);
                if (nig.keyCode == pos.keyCode && nig.isValid)
                {
                    nig.keyName = ParallelUtils.Concat(mNegative ? '-' : '+', nig.keyName);
                    return nig;
                }
                return mNegative ? nig : pos;
            }

            public bool SetAction(InputController.IInputAction action)
            {
                return false;
            }

            internal void OnTick(float deltaTime)
            {
                bool hasBtn = mNegative ? (mAxis.Value < -0.5f) : (mAxis.Value > 0.5f);
                if (hasBtn != mHasBtn)
                {
                    mHasBtn = hasBtn;
                    if (hasBtn)
                    {
                        IsDown = true;
                        IsUp = false;
                        HasButton = true;
                        mTime = -10;
                    }
                    else
                    {
                        IsDown = false;
                        IsUp = true;
                        HasButton = false;
                    }
                }
                else if (hasBtn)
                {
                    if (mTime > 0)
                    {
                        mTime -= deltaTime;
                    }
                    else if (HasButton)
                    {
                        HasButton = false;
                        IsUp = true;
                        IsDown = false;
                    }
                    else if (IsUp)
                    {
                        IsUp = false;
                        HasButton = false;
                        mTime = mTime < -0.6f ? 0.6f : Mathf.Min(mTime * 0.7f, 0.3f);
                    }
                    else
                    {
                        IsDown = true;
                        IsUp = false;
                        HasButton = true;
                    }
                }
                else
                {
                    IsDown = false;
                    IsUp = false;
                    HasButton = false;
                }
            }
        }

    }
}